/*
 * Copyright (C) 2009 Show SMS open source project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.bydavy.model.showsms;

//~--- non-JDK imports --------------------------------------------------------

import android.content.ContentValues;
import android.content.Context;

import android.database.Cursor;

import android.net.Uri;

import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;

import android.telephony.gsm.SmsMessage;

import android.text.format.DateFormat;

import com.bydavy.util.showsms.Log;

//~--- JDK imports ------------------------------------------------------------

import java.util.Date;

/**
 * Class that describe a <SSMessage>
 * A message have a id (message id in the database), a thread id (conversation id in the database), a sender <person>, a timeStamp (reception date) and a body
 *
 * @version        1.0, 09/06/21
 * @author         Davy L.
 */
public class SSMessage implements Parcelable {
    //~--- static fields ------------------------------------------------------

    /** Field description */
    private static final int CONTENT_SMS_READED_VALUE = 1;

    /** Key to bundle the body */
    public static final String EXTRAS_BODY = "EXTRAS_MESSAGE_BODY";

    /** Field description */
    public static final String EXTRAS_ID = "EXTRAS_MESSAGE_ID";

    /** Field description */
    public static final String EXTRAS_THREAD_ID = "EXTRAS_MESSAGE_THREAD_ID";

    /** Key to bundle the timestamp */
    public static final String EXTRAS_TIMESTAMP = "EXTRAS_MESSAGE_TIMESTAMP";

    /** ID value if message not founded in the database */
    private static final long NO_ID_FOUNDED = -1;

    /** THREAD_ID value if message not founded in the database */
    private static final long NO_THREAD_ID_FOUNDED = -1;

    /** Field description */
    public static final Uri SMS_CONTENT_URI = Uri.parse("content://sms");

    /** Field description */
    public static final Uri SMS_INBOX_CONTENT_URI = Uri.withAppendedPath(SMS_CONTENT_URI, "inbox");

    /** Field description */
    public static final Uri MMS_SMS_CONTENT_URI = Uri.parse("content://mms-sms/");

    /** Field description */
    public static final Uri THREAD_ID_CONTENT_URI = Uri.withAppendedPath(MMS_SMS_CONTENT_URI, "threadID");

    /** Creator of a SSMessage or SSMessages from a Parcel */
    public static final Parcelable.Creator<SSMessage> CREATOR = new Parcelable.Creator<SSMessage>() {
        public SSMessage createFromParcel(Parcel in) {
            return new SSMessage(in);
        }
        public SSMessage[] newArray(int size) {
            return new SSMessage[size];
        }
    };

    //~--- fields -------------------------------------------------------------

    /** message body */
    private String body;

    /** message id in the database */
    private long id;

    /** Sender person */
    private Person person;

    /** conversation id in the database */
    private long threadId;

    /** reception date (from the provider) */
    private long timeStamp;

    //~--- constructors -------------------------------------------------------

    /**
     * Construct a <SSMessage> from a parcel
     *
     * @param in parcel containing a <SSMessage>
     */
    public SSMessage(Parcel in) {
        id        = in.readLong();
        threadId  = in.readLong();
        person    = in.readParcelable(getClass().getClassLoader());
        timeStamp = in.readLong();
        body      = in.readString();
    }

    /**
     * Constructs from SmsMessage
     *
     * @param message
     * @param context
     */
    public SSMessage(SmsMessage message, Context context) {
        person    = new Person(message.getOriginatingAddress(), context);
        threadId  = findThreadId(context, person.getPhoneNumber());
        body      = message.getMessageBody();
        timeStamp = message.getTimestampMillis();
        id        = findId(threadId, body, context);
    }

    /**
     * Constructs from SmsMessage[] of a single sender
     *
     *
     * @param messages
     * @param context
     */
    public SSMessage(SmsMessage[] messages, Context context) {
        person    = new Person(messages[0].getOriginatingAddress(), context);
        threadId  = findThreadId(context, person.getPhoneNumber());
        timeStamp = messages[0].getTimestampMillis();
        id        = findId(threadId, body, context);
        body      = "";

        // Add body of each SMS
        for (int i = 0; i < messages.length; i++) {
            body += messages[i].getMessageBody();
        }
    }

    /**
     * Constructs from attributes
     * All attributes must be defined
     * @param _person message sender <person>
     * @param _body message body
     * @param _timeStamp message reception timestamp()from hte provider
     * @param _id message id in the database
     * @param _threadId conversation id in the database
     */
    public SSMessage(Person _person, String _body, long _timeStamp, long _id, long _threadId) {
        id        = _id;
        threadId  = _threadId;
        person    = _person;
        timeStamp = _timeStamp;
        body      = _body;
    }

    /**
     * Constructs from attributes
     * All attributes must be defined
     * @param phonenumber sender phone number (used to retrevied person information from phon contacts)
     * @param _body message body
     * @param _timeStamp message reception timestamp()from hte provider
     * @param _id message id in the database
     * @param _threadId conversation id in the database
     * @param context application context
     */
    public SSMessage(String phonenumber, String _body, long _timeStamp, long _id, long _threadId, Context context) {
        id        = _id;
        threadId  = _threadId;
        person    = new Person(phonenumber, context);
        timeStamp = _timeStamp;
        body      = _body;
    }

    //~--- get methods --------------------------------------------------------

    /**
     * Getter : the <SSMessage> id in the database
     *
     * @return <SSMessage> id in the databse
     */
    public long getId() {
        return id;
    }

    /**
     * Getter : the conversation id in the databse
     *
     * @return <SSMessage> thread id in the databse
     */
    public long getThreadId() {
        return threadId;
    }

    //~--- methods ------------------------------------------------------------

    /**
     * Constructs a SSMessage from bundle
     *
     * @param bundle
     *
     * @return a SSMessage or Null if no message bundled
     */
    public static SSMessage constructFromBundle(Bundle bundle) {
        long   id        = bundle.getLong(EXTRAS_ID);
        long   threadId  = bundle.getLong(EXTRAS_THREAD_ID);
        Person person    = Person.constructFromBundle(bundle);
        long   timeStamp = bundle.getLong(EXTRAS_TIMESTAMP);
        String body      = bundle.getString(EXTRAS_BODY);

        // test if a message is bundled
        if (person != null) {
            return new SSMessage(person, body, timeStamp, id, threadId);
        } else {
            return null;
        }
    }

    /**
     *   Add <SSMessage> bundle to an existing bundle
     *   /!\ One bundle object can't contains more that one <SSMessage>
     *
     *   @param b existing bundle
     *   @return <SSMessage> bundled
     */
    public Bundle toBundle(Bundle b) {
        b.putLong(EXTRAS_ID, id);
        b.putLong(EXTRAS_THREAD_ID, threadId);
        b = person.toBundle(b);
        b.putLong(EXTRAS_TIMESTAMP, timeStamp);
        b.putString(EXTRAS_BODY, body);

        return b;
    }

    /**
     * Bundle a <SSMessage> object
     *
     * @return <SSMessage> bundled
     */
    public Bundle toBundle() {
        return toBundle(new Bundle());
    }

    //~--- get methods --------------------------------------------------------

    /**
     * Getter : <SSMessage> Body
     *
     * @return <SSMessage> Body
     */
    public String getBody() {
        return body;
    }

    /**
     * Getter : <SSMessage> sender, a <person> object
     *
     * @return  <SSMessage> sender, a <person> object
     */
    public Person getPerson() {
        return person;
    }

    //~--- methods ------------------------------------------------------------

    /**
     * Extract <SSMessage> from a bundle containing <SmsMessage>
     *
     * @param bundle the bundle containing <SmsMessage<
     * @param context context application
     *
     * @return set of <SSMessage> retrieved from the bundle
     */
    public static SSMessage extract(Bundle bundle, Context context) {
        SSMessage aMessage = null;

        if (bundle != null) {
            Object[]     pdus     = (Object[]) bundle.get("pdus");
            SmsMessage[] messages = new SmsMessage[pdus.length];

            for (int i = 0; i < pdus.length; i++) {
                messages[i] = SmsMessage.createFromPdu((byte[]) pdus[i]);
            }

            Log.v("SSMessage : extract() : " + messages.length + " message(s) received");
            aMessage = new SSMessage(messages, context);
            Log.v("SSMessage : extract() : message from |" + aMessage.getPerson().getPhoneNumber() + "| contains |"
                  + aMessage.getBody() + "|");
        }

        return aMessage;
    }

    //~--- get methods --------------------------------------------------------

    /**
     * Getter : timeStamp date (reception  date from the provider)
     * The date is formatting with the standard defined by th android langage
     * @param context application context
     * @return date(reception  date from the provider)
     */
    public String getDate(Context context) {
        Date date = new Date(timeStamp);

        return DateFormat.getDateFormat(context).format(date);
    }

    /**
     * Getter : timeStamp hour (reception  date from the provider)
     * The hour is formatting with the standard defined by th android langage
     * @param context
     *
     * @return hour (reception  hour from the provider)
     */
    public String getHour(Context context) {
        Date date = new Date(timeStamp);

        return DateFormat.getTimeFormat(context).format(date);
    }

    //~--- methods ------------------------------------------------------------

    /**
     * Method description
     *
     *
     * @return
     */
    @Override
    public int describeContents() {

        // TODO Auto-generated method stub
        return 0;
    }

    /**
     * Save the <SSMessage> in a parcel
     *
     * @param dest parcel'll contains the <SSMessage>
     * @param flags
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeLong(id);
        dest.writeLong(threadId);
        dest.writeParcelable(person, Person.PARCELABLE_WRITE_RETURN_VALUE);
        dest.writeLong(timeStamp);
        dest.writeString(body);
    }

    /**
     * All SSMessage informations in a String ()
     *
     * With this Layout:
     * Person informations (defined in Person.toString())
     *
     * @return
     */
    @Override
    public String toString() {
        return person + "SSMessage id : " + id + "\n" + "SSMessage thread id : " + threadId + "\n"
               + "SSMessage timeStamp : " + timeStamp + "\n" + "SSMessage body : " + body;
    }

    /**
     * Find message thread id from sender phoneNumber in the database
     *
     * @param context application context
     * @param phoneNumber sender message phone number
     *
     * @return thread id from the database, or SSMessage.NO_THREAD_ID_FOUNDED if the message doesn't exist in hte database
     */
    public static long findThreadId(Context context, String phoneNumber) {
        String      THREAD_RECIPIENT_QUERY = "recipient";
        Uri.Builder uriBuilder             = THREAD_ID_CONTENT_URI.buildUpon();

        uriBuilder.appendQueryParameter(THREAD_RECIPIENT_QUERY, phoneNumber);

        String[] selection = new String[] { "_id" };
        long     threadId  = NO_THREAD_ID_FOUNDED;
        Cursor   cursor    = context.getContentResolver().query(uriBuilder.build(), selection, null, null, null);

        if (cursor != null) {
            try {
                if (cursor.moveToFirst()) {
                    threadId = cursor.getLong(0);
                }
            } finally {
                cursor.close();
            }
        }

        Log.v("Found thread id: " + threadId);

        return threadId;
    }

    /**
     * Mark the current <SSMessage> as readed in the database
     * The mark as read status is simply updated, so this method can be called several times (return true each time)
     * @param context application context
     *
     * @return true if mark as read status was write successfully, otherwise false (error occured)
     */
    public boolean markAsRead(Context context) {
        if (markAsReadAMessage(id, context) != 0) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Mark the as readed in the database the message that as the id <message_id>
     * The mark as read status is simply updated, so this method can be called several times
     *
     * @param message_id message id of the message to mark as readed
     * @param context application context
     *
     * @return 0 if the make as read status wasn't updated, otherwise return a positive integer
     */
    private static int markAsReadAMessage(long message_id, Context context) {
        Uri           messageUri = Uri.withAppendedPath(SMS_CONTENT_URI, String.valueOf(message_id));
        ContentValues values     = new ContentValues(1);

        values.put("read", CONTENT_SMS_READED_VALUE);
        Log.v("messageUri for marking message read: " + messageUri.toString());

        int result = 0;

        try {
            result = context.getContentResolver().update(messageUri, values, null, null);
        } catch (Exception e) {
            Log.v("Error during setting message readed");
        }

        Log.v("Message " + message_id + " marked as read, result = " + result);

        return result;
    }

    /**
     * Method description
     *
     *
     * @param context
     */
    public void delete(Context context) {
        Uri uri = Uri.withAppendedPath(SMS_CONTENT_URI, String.valueOf(id));

        context.getContentResolver().delete(uri, null, null);
    }

    /**
     * Find message id from his thread id and his body in the database
     *
     * @param threadId message thread id of the message to find
     * @param body message body of the message to find
     * @param context application context
     *
     * @return message id from the database, or SSMessage.NO_ID_FOUNDED if the message doesn't exist in hte database
     */
    public static long findId(long threadId, String body, Context context) {
        long     id         = NO_ID_FOUNDED;
        String[] projection = new String[] { "_id" };
        String   selection  = new String("thread_id=" + threadId);
        String   order      = new String("date desc");
        Cursor   cursor     = context.getContentResolver().query(SMS_INBOX_CONTENT_URI, projection, selection, null,
                                  order);

        if (cursor != null) {
            try {
                if (cursor.moveToFirst()) {
                    int idColumn = cursor.getColumnIndex("_id");

                    id = cursor.getLong(idColumn);
                }
            } finally {
                cursor.close();
            }
        }

        Log.v("Found id message: " + id);

        return id;
    }
}
